///////////////////////////////////////////////////////////////////////////////////////////////////
/// \file formic/test_driver/utilities.cpp
///
/// \brief   implementation file for some functions used in testing
///
///////////////////////////////////////////////////////////////////////////////////////////////////

#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include<cmath>

#include <boost/format.hpp>

#include<formic/test_driver/utilities.h>
#include<formic/exception.h>

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief Searches the provided input stream for a line containing the specified string.
///        Note that the input stream is not returned to its initial state after searching.
///
/// \param[in]      is      the input stream to search
/// \param[out]     line    on successful exit, the line containing the search string
/// \param[in]      str     the string to search for
///
/// \return  true if the string was found and false otherwise
///
///////////////////////////////////////////////////////////////////////////////////////////////////
bool formic::test::get_line_containing_str(std::istream & is, std::string & line, const std::string & str) {

  std::string ln;
  while ( std::getline(is, ln) ) {
    if (ln.find(str) != std::string::npos) {
      line = ln;
      return true;
    }
  }
  return false;

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief Searches the given file for the floating point number immediately following
///        the nth occurance of the supplied search string and tests whether that number is
///        sufficiently close to its correct value.
///        element of the vector corresponds to the no-derivative term.
///
/// \param[in]      float_name    a string describing the floating point number
/// \param[in]      file_name     the name of the file to search
/// \param[in]      search_str    the string to search for
/// \param[in]      n             The occurance of the search string to use.
///                               0 is the first occurance, 1 the second, etc.
///                               -1 is the last occurance, -2 the next-to-last, etc.
/// \param[in]      target_val    the "correct" value for the read-in number
/// \param[in]      tolerance     the tolerance for how close we must be to the correct value
/// \param[in]      verbose       whether or not to write comments to the standard output
///
/// \return  false if the test fails for any reason, true otherwise
///
///////////////////////////////////////////////////////////////////////////////////////////////////
bool formic::test::compare_float(const std::string & float_name,
                                 const std::string & file_name,
                                 const std::string & search_string,
                                 int n,
                                 const double target_value,
                                 const double tolerance,
                                 const bool verbose) {

  // local function to initialzie the search buffer
  struct local_funcs {
    static void init_buffer(std::ifstream & ifs, std::string & buff) {
      for (size_t i = 0; i < buff.size() && ifs; i++) {
        char c;
        if ( ifs.get(c) );
          buff.replace(i, 1, 1, c);
      }
    }
  };

  // initialize a vector to hold the occurances of the desired float
  std::vector<double> found_floats;

  // open the file
  std::ifstream ifs(file_name.c_str());

  // fail if file did not open
  if (!ifs.is_open()) {
    if (verbose)
      std::cout << boost::format("Error:  failed to open file \"%s\" when searching for %s") % file_name % float_name << std::endl;
    return false;
  }

  // initialize the read in buffer
  std::string buff(search_string);
  local_funcs::init_buffer(ifs, buff);

  //std::cout << "search string = \"" << search_string << "\"\n" << std::endl;

  //std::cout << "initial\nbuffer = \"" << buff << "\"\n" << std::endl;

  // read in all instances of the desired floating point number
  while ( ifs ) {

    //std::cout << "buffer = \"" << buff << "\"" << std::endl;

    // if we have a match, read in the number
    if (buff == search_string) {

      // read the number
      double number = 0.0;
      if ( ifs >> number )
        found_floats.push_back(number);

      // refill the buffer
      if ( ifs )
        local_funcs::init_buffer(ifs, buff);

    // otherwise, increment the buffer by one
    } else {

      // delete first character in buffer
      buff.erase(0, 1);

      // append next character in file
      buff.append(1, char(ifs.get()));

    }

  }

  // exit with failure if no numbers were found
  if ( found_floats.size() == 0 ) {
    if (verbose)
      std::cout << boost::format("Error:  failed to find search string \"%s\" in file \"%s\" when searching for %s") % search_string % file_name % float_name << std::endl;
    return false;
  }

  //std::cout << "input n = " << n << std::endl;

  // a negative occurance means we should count backwards
  if ( n < 0 )
    n = found_floats.size() + n;

  //std::cout << "modified n = " << n << std::endl;

  // exit with failure if not enough occurances were found
  if ( n < 0 || n >= found_floats.size() ) {
    if (verbose)
      std::cout << boost::format("Error:  too few occurances were found for %s") % float_name << std::endl;
    return false;
  }

  // get the number corresponding to the desired occurance
  const double val = found_floats.at(n);

  // check that the number is equal to the desired value within the specified tolerance
  if ( std::abs( val - target_value ) > tolerance ) {
    if (verbose)
      std::cout << boost::format("%s %s INCORRECT") % float_name % std::string(40 - float_name.size(), '.') << std::endl;
    return false;
  }

  // print out a correct result indicator
  if (verbose)
    std::cout << boost::format("%s %s   correct") % float_name % std::string(40 - float_name.size(), '.') << std::endl;

  // tell the calling function the test was successful
  return true;

}
